#ifndef CFG_ADAPTERS_HPP
#define CFG_ADAPTERS_HPP

#include "data/cfg_items.hpp"
#include <concepts>

template <typename T, typename Stringify, typename Validator>
class ValueAdapter: public IValueAdapter {
public:
    template<typename ... VP>
    ValueAdapter(T *mem, VP&& ... vp): mem_(mem), v_(std::forward<VP>(vp)...) {}
    ~ValueAdapter() {}

    std::string get() const final {
        return s_.to_string(mem_);
    }
    bool set(const std::string &input, std::ostream &err_output) override {
        T value{};
        if (s_.try_parse(input, &value, err_output))
        {
            if (v_.validate(value, err_output)) {
                *mem_ = std::move(value);
                return true;
            }
        }
        return false;
    }
private:
    T        *mem_;
    Validator v_;
    Stringify s_;
};

template <typename T>
class NullValidator {
public:
    bool validate(const T&, std::ostream&) const {
        return true;
    }
};

template <typename T>
class RangeValidator {
public:
    RangeValidator(T min, T max): min_(std::move(min)), max_(std::move(max)) {}

    bool validate(const T& v, std::ostream& err) const {
        if (min_ <= v && max_ >= v) {
            return true;
        }
        err << "out of range!\n";
        return false;
    }
private:
    T min_;
    T max_;
};

template <typename T>
class GenericStringify;

template <std::signed_integral T>
class GenericStringify<T> {
public:
    std::string to_string(const T *mem) const {
        return std::to_string(*mem);
    }
    bool try_parse(const std::string &input, T *out, std::ostream &err) const {
        char *end;
        *out = static_cast<T>(std::strtoll(input.data(), &end, 0));
        if (end == &input[0]) {
            err << "parse error, code is " << errno << '\n';
            return false;
        }
        return true;
    }
};

template <std::unsigned_integral T>
class GenericStringify<T> {
public:
    std::string to_string(const T *mem) const {
        return std::to_string(*mem);
    }
    bool try_parse(const std::string &input, T *out, std::ostream &err) const {
        char *end;
        *out = static_cast<T>(std::strtoull(input.data(), &end, 0));
        if (end == &input[0]) {
            err << "parse error, code is " << errno << '\n';
            return false;
        }
        return true;
    }
};

template <std::floating_point T>
class GenericStringify<T> {
public:
    std::string to_string(const T *mem) const {
        return std::to_string(*mem);
    }
    bool try_parse(const std::string &input, T *out, std::ostream &err) const {
        char *end;
        *out = static_cast<T>(std::strtod(input.data(), &end));
        if (end == &input[0]) {
            err << "parse error, code is " << errno << '\n';
            return false;
        }
        return true;
    }
};

template <>
class GenericStringify<std::string> {
public:
    std::string to_string(const std::string *mem) const {
        return *mem;
    }
    bool try_parse(const std::string &input, std::string *out, std::ostream &err) const {
        out->assign(input);
        return true;
    }
};

template <typename T>
using NoCheckedValue = ValueAdapter<T, GenericStringify<T>, NullValidator<T>>;

template <typename T>
using RangedValue = ValueAdapter<T, GenericStringify<T>, RangeValidator<T>>;

#define NEW_NC(V) new NoCheckedValue<decltype(V)>{&V}
#define NEW_RC(V,L,H) new RangedValue<decltype(V)>{&V,L,H}

#endif /* CFG_ADAPTERS_HPP */
